﻿using System;
using System.IO;
using CoreEx.Common.Extensions;
using CoreEx.Common.Logging;
using CoreEx.Database.Metadata;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;

namespace CoreEx.Database.MetaData.Implementation
{
    /// <summary>
    /// This class generates C# entity types based on the content of the <see cref="IMetadataRepository"/>.
    /// </summary>
    [Implements(typeof(IModelGenerator))]
    public class ModelGenerator : IModelGenerator, IInitialize
    {
        /// <summary>
        /// Generates entity types for each entry in the <see cref="IMetadataRepository"/>.
        /// </summary>
        /// <param name="metadataRepository">The <see cref="IMetadataRepository"/> that contains information about the structure of the DBMS.</param>
        /// <param name="nameSpace">The namespace to be used for the generated types.</param>
        /// <param name="outputDirectory">The directory where the generated files will be placed.</param>
        /// <param name="generateEquals">Indicates if the Equals and GetHashCode methods should be overridden.</param>
        public void Generate(IMetadataRepository metadataRepository, string nameSpace, string outputDirectory, bool generateEquals)
        {
            this.LogInfo(string.Format("Starting generation of entities based on repository {0}. The output directory is: {1}",
                                       metadataRepository.Name,outputDirectory));

            CreateDirectory(outputDirectory);

            foreach (var tableInfo in metadataRepository.Tables)
            {
                var path = GetFullPath(tableInfo, outputDirectory);
                if (ShouldGenerate(tableInfo, path))
                {
                    var fileStream = new FileStream(GetFullPath(tableInfo, outputDirectory), FileMode.Create);
                    var streamWriter = new StreamWriter(fileStream);
                    TypeWriter.Generate(tableInfo, nameSpace, streamWriter, generateEquals);
                    streamWriter.Flush();
                    streamWriter.Close();
                    this.LogInfo(string.Format("File {0} generated for table {1}",path,tableInfo.TableName));
                }
            }
            this.LogInfo(string.Format("Finished generation of entities based on repository {0}. The output directory is: {1}",
                                       metadataRepository.Name, outputDirectory));
        }


        private static void CreateDirectory(string directory)
        {
            if (!Directory.Exists(directory))
                Directory.CreateDirectory(directory);
        }

        private bool ShouldGenerate(TableInfo tableInfo, string path)
        {
            return (DefinesPrimaryKey(tableInfo) && IsValidPath(tableInfo, path));            
        }

        private bool DefinesPrimaryKey(TableInfo tableInfo)
        {
            if (tableInfo.PrimaryKey == null)
                this.LogWarning(string.Format(
                                    "Unable to generate class for table '{0}'. The table does not define a primary key.",
                                    tableInfo));
            return tableInfo.PrimaryKey != null;
        }

        private bool IsValidPath(TableInfo tableInfo, string path)
        {
            if (File.Exists(path))
            {
                var fileInfo = new FileInfo(path);
                if (fileInfo.IsReadOnly)
                {
                    this.LogWarning(
                        string.Format(
                            "Operation aborted. Unable to generated entity for table {0} The file {1} is read only. Make sure that the file checked out if regeneration was intended.",
                            tableInfo.TableName, path));
                    return false;
                }
            }
            return true;
        }




        private string GetFileName(TableInfo tableInfo)
        {
            return string.Format("{0}.cs", tableInfo.TableName.ToSingular());
        }

        private string GetFullPath(TableInfo tableInfo, string outputDirectory)
        {
            string path = Path.Combine(outputDirectory, GetFileName(tableInfo));
            return path;
        }


        /// <summary>
        /// Gets or sets the <see cref="ITypeWriter"/> instance that is responsible for generating the code. 
        /// </summary>
        public ITypeWriter TypeWriter { get; set; }


        /// <summary>
        /// Initializes this service instance.
        /// </summary>        
        public void Initialize(IServiceContainer source)
        {
            TypeWriter = source.GetService<ITypeWriter>();
        }
    }
}
